package database

import (
	"fmt"
	"time"

	. "gddgame.cc/galaxy/common/dsl/web"
	"gddgame.cc/galaxy/common/feature/supernova"
	"gddgame.cc/galaxy/utils/def"
	"gddgame.cc/galaxy/utils/serialize/msgpack"
)

// 数据库管理
// 建立不同环境，确保数据结构一致
// 建立数据快照，快速更新数据

var (
	s = msgpack.Msgpack(false)
)

func refreshEnvironment(environment *Environment) error {
	tableList, err := refreshEnvironmentTable(environment)
	if err != nil {
		return err
	}
	if _, err := EnvironmentModel.PK(environment.Id).Update(map[string]interface{}{
		"tables":    tableList,
		"last_time": time.Now(),
	}); err != nil {
		return err
	}
	return nil
}

func refreshEnvironmentTable(environment *Environment) ([]EnvironmentTable, error) {
	var region Region
	if _, err := RegionModel.PK(environment.RegionId).One(&region); err != nil {
		return nil, err
	}
	engine, err := supernova.NewEngineWithDsn(environment.DSN)
	if err != nil {
		return nil, err
	}
	defer engine.Close()
	if region.Preference != nil {
		for _, item := range region.Preference {
			engine.Set(item.Key, item.Value)
		}
	}
	tables, err := engine.AnalysisDatabase(region.Prefix)
	if err != nil {
		return nil, err
	}
	tmpTable := map[string]*EnvironmentTable{}
	for index, table := range environment.Tables {
		tmpTable[table.Table.Name] = &environment.Tables[index]
	}
	tableList := make([]EnvironmentTable, 0, len(tables))
	for index := range tables {
		table := tables[index]
		v, ok := tmpTable[table.Name]
		if ok {
			v.Table = table
		} else {
			v = &EnvironmentTable{
				Table:         table,
				Name:          table.Name,
				IncludeStruct: true,
				IncludeRecord: true,
			}
		}
		tableList = append(tableList, *v)
	}
	return tableList, nil
}

func saveEnvironmentTable(environment *Environment, snapshot *Snapshot) error {
	var region Region
	if _, err := RegionModel.PK(environment.RegionId).One(&region); err != nil {
		return err
	}
	engine, err := supernova.NewEngineWithDsn(environment.DSN)
	if err != nil {
		return err
	}
	defer engine.Close()
	if region.Preference != nil {
		for _, item := range region.Preference {
			engine.Set(item.Key, item.Value)
		}
	}
	tables, err := engine.AnalysisDatabase(region.Prefix)
	if err != nil {
		return err
	}
	tmpTable := map[string]*EnvironmentTable{}
	for index := range environment.Tables {
		table := environment.Tables[index]
		tmpTable[table.Table.Name] = &table
	}
	resultTable := map[string]*SnapshotTable{}
	operations := []supernova.Operation{}
	for index := range tables {
		table := tables[index]
		v, ok := tmpTable[table.Name]
		if !ok {
			resultTable[table.Name] = &SnapshotTable{
				Table:         table,
				Name:          table.Name,
				Records:       0,
				IncludeStruct: true,
				IncludeRecord: true,
			}
			continue
		}
		resultTable[table.Name] = &SnapshotTable{
			Records:       0,
			Name:          table.Name,
			IncludeStruct: v.IncludeStruct,
			IncludeRecord: v.IncludeRecord,
		}
		if v.IncludeStruct {
			resultTable[table.Name].Table = table
		}
	}
	tableList := make([]SnapshotTable, 0, len(resultTable))
	for _, table := range resultTable {
		tableList = append(tableList, *table)
	}
	for index := range tableList {
		table := tableList[index]
		if table.IncludeRecord {
			operations = append(operations, supernova.AllRecords(table.Table))
		}
	}
	result, err := engine.QueryDatabase(operations, nil)
	if err != nil {
		return err
	}

	tableResult := make([][][]interface{}, len(result))
	ii := 0
	for index := range tableList {
		table := &tableList[index]
		if table.IncludeRecord {
			if result[ii] == nil {
				tableResult[ii] = nil
			} else {
				tableResult[ii] = result[ii].([][]interface{})
				table.Records = len(tableResult[ii])
			}
			fmt.Println("add record", table.Name)
			ii += 1
		}
	}
	b, err := s.Marshal(tableResult)
	if err != nil {
		return err
	}
	_, _ = SnapshotModel.PK(snapshot.Id).Update(map[string]interface{}{
		"tables":      tableList,
		"result":      b,
		"process":     Success,
		"finish_time": time.Now(),
	})
	return nil
}

func recoveryEnvironmentTable(environment *Environment, snapshot *Snapshot, record *Record) error {
	var region Region
	if _, err := RegionModel.PK(environment.RegionId).One(&region); err != nil {
		return err
	}
	engine, err := supernova.NewEngineWithDsn(environment.DSN)
	if err != nil {
		return err
	}
	defer engine.Close()
	if region.Preference != nil {
		for _, item := range region.Preference {
			engine.Set(item.Key, item.Value)
		}
	}

	descTable := []*supernova.Table{}
	tmpSnapshotTable := map[string]*SnapshotTable{}
	for index := range snapshot.Tables {
		table := snapshot.Tables[index]
		tmpSnapshotTable[table.Name] = &table
		if table.GetTable() == nil {
			continue
		}
		descTable = append(descTable, table.GetTable())
	}

	tables, err := engine.AnalysisDatabase(region.Prefix)
	if err != nil {
		return err
	}
	tmpTable := map[string]*EnvironmentTable{}
	tableList := make([]EnvironmentTable, 0, len(tables))
	for index := range tables {
		table := tables[index]
		if v, ok := tmpSnapshotTable[table.Name]; ok {
			if !v.IncludeStruct {
				continue
			}
		}
		v, ok := tmpTable[table.Name]
		if ok {
			v.Table = table
		} else {
			v = &EnvironmentTable{
				Table:         table,
				Name:          table.Name,
				IncludeStruct: true,
				IncludeRecord: true,
			}
		}
		tableList = append(tableList, *v)
	}

	sourceTable := make([]*supernova.Table, len(tableList))
	for index := range tableList {
		sourceTable[index] = tableList[index].GetTable()
	}
	diffSlice := supernova.NewDiffSlice(sourceTable, descTable)
	operations := diffSlice.Operation()

	var result [][][]interface{}
	if snapshot.Result != nil && len(snapshot.Result) > 0 {
		if err := s.Unmarshal(snapshot.Result, &result); err != nil {
			return err
		}
	}

	ii := 0
	for index := range snapshot.Tables {
		table := snapshot.Tables[index]
		if record.ClearRecord {
			operations = append(operations, supernova.ClearRecords(table.Table))
		}
		if table.IncludeRecord {
			items := result[ii]
			ii += 1
			fmt.Println("recovery record", table.Name, table.Records, len(items))
			if items == nil {
				continue
			}
			if record.ClearRecord {
				operations = append(operations, supernova.AddRecords(table.Table, items))
			} else {
				operations = append(operations, supernova.ReplaceRecords(table.Table, items))
			}
		}
	}
	if err := engine.ExecDatabase(operations, nil); err != nil {
		return err
	}
	_, _ = RecordModel.PK(record.Id).Update(map[string]interface{}{
		"process":     Success,
		"finish_time": time.Now(),
	})
	return nil
}

func SaveEnvironment(context def.Context, environmentId int, remark string, callback func(err error)) (int, error) {
	var environment Environment
	var err error
	if _, err := EnvironmentModel.PK(environmentId).One(&environment); err != nil {
		return 0, SystemException(err)
	}
	snapshot := Snapshot{
		RegionId:      environment.RegionId,
		EnvironmentId: environment.Id,
		Process:       Wait,
		Remark:        remark,
		Type:          ManualSnapshotType,
	}
	id, err := SnapshotModel.Insert(&snapshot)
	if err != nil {
		return 0, SystemException(err)
	}
	go func() {
		_, _ = SnapshotModel.PK(snapshot.Id).Update(map[string]interface{}{
			"process": Running,
		})
		if err := saveEnvironmentTable(&environment, &snapshot); err != nil {
			_, _ = SnapshotModel.PK(snapshot.Id).Update(map[string]interface{}{
				"process":     Fail,
				"finish_time": time.Now(),
			})
			if callback != nil {
				callback(err)
			}
			fmt.Println(err)
		} else {
			if callback != nil {
				callback(nil)
			}
		}
	}()

	return int(id), nil
}

func RecoveryEnvironment(context def.Context, environmentId int, snapshotId int, clear bool, callback func(err error)) (int, error) {
	var environment Environment
	var snapshot Snapshot
	if _, err := EnvironmentModel.PK(environmentId).One(&environment); err != nil {
		return 0, SystemException(err)
	}
	if _, err := SnapshotModel.PK(snapshotId).One(&snapshot); err != nil {
		return 0, SystemException(err)
	}

	if snapshot.FinishTime.After(time.Now()) {
		return 0, ValidException("快照还未执行完")
	}
	if snapshot.Process == Fail {
		return 0, ValidException("快照执行失败")
	}
	record := Record{
		RegionId:      environment.RegionId,
		EnvironmentId: environment.Id,
		SnapshotId:    snapshot.Id,
		ClearRecord:   clear,
		Process:       Wait,
	}
	_, err := RecordModel.Insert(&record)
	if err != nil {
		return 0, SystemException(err)
	}
	var snapshotAuto Snapshot
	if snapshot.Type == ManualSnapshotType {
		snapshotAuto = Snapshot{
			RegionId:      environment.RegionId,
			EnvironmentId: environment.Id,
			Process:       Wait,
			Remark:        "自动备份",
			Type:          AutoSnapshotType,
		}
		_, err = SnapshotModel.Insert(&snapshotAuto)
		if err != nil {
			return 0, SystemException(err)
		}
	}

	go func() {
		if snapshot.Type == ManualSnapshotType {
			_, _ = SnapshotModel.PK(snapshotAuto.Id).Update(map[string]interface{}{
				"process": Running,
			})
			if err := saveEnvironmentTable(&environment, &snapshotAuto); err != nil {
				_, _ = SnapshotModel.PK(snapshotAuto.Id).Update(map[string]interface{}{
					"process":     Fail,
					"finish_time": time.Now(),
				})
				_, _ = RecordModel.PK(record.Id).Update(map[string]interface{}{
					"process":     Fail,
					"result":      err.Error(),
					"finish_time": time.Now(),
				})
				if callback != nil {
					callback(err)
				}
				return
			}
		}
		_, _ = RecordModel.PK(record.Id).Update(map[string]interface{}{
			"process": Running,
		})
		if err := recoveryEnvironmentTable(&environment, &snapshot, &record); err != nil {
			_, _ = RecordModel.PK(record.Id).Update(map[string]interface{}{
				"process":     Fail,
				"result":      err.Error(),
				"finish_time": time.Now(),
			})
			if callback != nil {
				callback(err)
			}
			fmt.Println(err)
		} else {
			if callback != nil {
				callback(nil)
			}
		}
	}()
	if snapshotAuto.Id > 0 {
		return snapshotAuto.Id, nil
	}
	return 0, nil
}
